package com.yifeng.repo.apm.endpoint.logging.log4j2;

import com.alibaba.fastjson.JSON;
import com.yifeng.repo.apm.endpoint.console.api.model.LogConfTypeEnum;
import com.yifeng.repo.apm.endpoint.sender.LogSender;
import com.yifeng.repo.apm.endpoint.sender.LogSenderManager;
import com.yifeng.repo.apm.endpoint.utils.BaseUtil;
import next.fire.spinus.logx.api.model.LevelEnum;
import next.fire.spinus.logx.api.model.LogData;
import next.fire.spinus.logx.utils.env.HostManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Created by daibing on 2020/9/17
 */
@Plugin(name = TraceLog4j2Appender.PLUGIN_NAME,
        category = Node.CATEGORY,
        elementType = Appender.ELEMENT_TYPE,
        printObject = true)
public class TraceLog4j2Appender extends AbstractAppender {
    public static final String PLUGIN_NAME = "LOGX";
    private final String appId;
    private final String appStage;
    private final String hostName;

    protected TraceLog4j2Appender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout, true, Property.EMPTY_ARRAY);
        HostManager util = HostManager.get();
        appId = util.getEnvSetting("app.id", "app_id");
        appStage = util.getEnvSetting("app.stage", "app_stage");
        hostName = util.getHostName();
        LogSenderManager.startupLogSender();
    }

    @Override
    public void append(LogEvent event) {
        LogSender logSender = LogSenderManager.getLogSender(true);
        if (!logSender.isLogEnabled(LogConfTypeEnum.CLOUD)) {
            return;
        }
        StackTraceElement source = event.getSource();
        if (!logSender.matched(source.getClassName(), LevelEnum.get(event.getLevel().toString()))) {
            return;
        }
        LogData logData = new LogData();
        logData.setAppId(this.appId);
        logData.setStage(this.appStage);
        logData.setLevel(LevelEnum.get(event.getLevel().toString()));
        logData.setLogTime(event.getTimeMillis());
        logData.setTraceId(BaseUtil.getTraceId());
        logData.setFrontendId(BaseUtil.getFrontendId());
        logData.setHost(this.hostName);
        logData.setThread(event.getThreadName());
        logData.setLocation(source.getClassName() + ":" + source.getMethodName() + ":" + source.getLineNumber());
        String message = event.getMessage().getFormattedMessage();
        if (BaseUtil.isNotBlank(message)) {
            logData.setMessage(message);
        }
        if (event.getThrown() != null) {
            logData.setThrowable(this.render(event.getThrown()));
        }
        logSender.send(logData);
    }

    @Override
    public boolean stop(long timeout, TimeUnit timeUnit) {
        LogSenderManager.shutdownSender();
        boolean stopped = super.stop(timeout, timeUnit, false);
        this.setStopped();
        return stopped;
    }

    @PluginBuilderFactory
    public static TraceLog4j2Appender.Builder newBuilder() {
        return new Builder();
    }

    public static class Builder extends org.apache.logging.log4j.core.appender.AbstractAppender.Builder implements org.apache.logging.log4j.core.util.Builder<TraceLog4j2Appender> {
        public Builder() {
            super();
        }

        @Override
        public TraceLog4j2Appender build() {
            String name = this.getName();
            if (name == null) {
                LOGGER.info("No name provided for LogxAppender");
                return null;
            }
            return new TraceLog4j2Appender(this.getName(), this.getFilter(), this.getOrCreateLayout(StandardCharsets.UTF_8));
        }
    }

    private String render(Throwable t) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            t.printStackTrace(pw);
        } catch (RuntimeException e) {
            // ignore.
        }
        pw.flush();
        LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString()));
        List<String> lines = new ArrayList<>();
        try {
            String line = reader.readLine();
            while (line != null) {
                lines.add(line);
                line = reader.readLine();
            }
        } catch (IOException e) {
            if (e instanceof InterruptedIOException) {
                Thread.currentThread().interrupt();
            }
            lines.add(e.toString());
        }
        return JSON.toJSONString(lines);
    }

}
